package marek.ai;
/**
 * By Marek Jankowski
 * jankowski.marek@gmail.com
 * Any comments or questions are welcome.
 * 
 * License: GNU GPL v3: http://www.gnu.org/licenses/gpl.html
 */

/**
 * Simple implementation of perceptron algo with optimised weight initialisation.
 * Use positiveSet and negativeSet arrays to train the perceptron.
 */
public class Perceptron {
	int epoch = 0;
	boolean trained = false;
	private double threshold; 
	private double[] weights = {0.0, 0.0, 0.0};
//	private double positiveSet[][] = {
//			{-1.5, -0.2, 1},
//			{0.0, -0.5, 1},
//			{-1.0, -1.0, 1}};
//
//	private double negativeSet[][] = {
//			{-2.0, -0.6, 1},
//			{-1.5, -1.0, 1},
//			{-1.6, -1.5, 1}};
	private double positiveSet[][] = {
			{0.5, 1.8, 1},
			{2.0, 1.5, 1},
			{1.0, 1.0, 1}};

	private double negativeSet[][] = {
			{0, 1.4, 1},
			{0.5, 1.0, 1},
			{0.4, 0.5, 1}};

//	private double positiveSet[][] = {
//			{0, 1.8, 1},
//			{2, 0.6, 1}};
//
//	private double negativeSet[][] = {
//			{-1.2, 1.4, 1},
//			{0.4, -1, 1}};

	public Perceptron() {
		initWeights();
	}
	private void initWeights() {
		double init[] = {0,0,0};
		for (int i = 0; i < positiveSet.length; i++) {
			double[] pos = positiveSet[i];
			for (int j = 0; j < pos.length; j++) {
				init[j] += pos[j];
			}
		}
		for (int i = 0; i < negativeSet.length; i++) {
			double[] neg = negativeSet[i];
			for (int j = 0; j < neg.length; j++) {
				init[j] -= neg[j];
			}
		}
		weights[0] = init[0];
		weights[1] = init[1];
		weights[2] = init[2];
		System.out.println("init w0, w1, w3 = " + init[0] + ", " + init[1] + ", " + init[2]);;
	}
	public boolean train() {
			System.out.println("Running epoch # " + (++epoch));
			int errorCount = 0;
			for (int i = 0; i < positiveSet.length; i++) {
				printWeights();
				double sumResult = sum(positiveSet[i]);
				if (sumResult <= -weights[weights.length-1]) {
					errorCount++;
					for (int j = 0; j < positiveSet[i].length; j++) {
						weights[j] += positiveSet[i][j];
					}
				}
			}
			for (int i = 0; i < negativeSet.length; i++) {
				printWeights();
				double sumResult = sum(negativeSet[i]);
				if (sumResult > -weights[weights.length-1]) {
					errorCount++;
					for (int j = 0; j < negativeSet[i].length; j++) {
						weights[j] -= negativeSet[i][j];
					}
				}
			}
			threshold = weights[weights.length-1];
			if (errorCount == 0 || epoch >= 100) {
				trained = true;
			}
			return trained;
	}

	private void printWeights() {
		System.out.print("Weights: ");
		for (int i = 0; i < weights.length; i++) {
			System.out.print(weights[i]);
			if (i + 1 < weights.length) {
				System.out.print(", ");
			}
		}
		System.out.println();
	}
	
	public double getThreshold() {
		return threshold;
	}

	public int evaluate(double[] input) {
		//System.out.println("Evaluating: {" + input[0] + ", " + input[1] + "}");
		return sum(input) > -threshold ? 1 : 0;
	}
	
	private double sum(double[] xSet) {
		double returnSum = 0;
		for (int i = 0; i < xSet.length-1; i++) {
			returnSum += xSet[i] * weights[i];
		}
		return returnSum;
	}
	
	public double[][] getPositiveSet() {
		return positiveSet;
	}

	public void setPositiveSet(double[][] positiveSet) {
		this.positiveSet = positiveSet;
	}

	public double[][] getNegativeSet() {
		return negativeSet;
	}

	public void setNegativeSet(double[][] negativeSet) {
		this.negativeSet = negativeSet;
	}

	public double[] getWeights() {
		return weights;
	}

	public void setWeights(double[] weights) {
		this.weights = weights;
	}


	public static void main(String[] args) {
		Perceptron p = new Perceptron();
		while (!p.train()) {};
		double[] testCase = {2.0, 2.0, 1};
		System.out.println(p.evaluate(testCase));
	}

}
